﻿using DotNetCore.CAP;
using Essensoft.Paylink.WeChatPay;
using Essensoft.Paylink.WeChatPay.V2;
using Essensoft.Paylink.WeChatPay.V2.Notify;
using Essensoft.Paylink.WeChatPay.V2.Request;
using Microsoft.Extensions.Options;
using Shop.Core.Config;
using Shop.Core.Enums;
using Shop.Core.Helper;
using Shop.Core.Redis;
using Shop.Core.Repository;
using Shop.Core.Share;
using Shop.Core.YOP;
using Shop.IServices;
using Shop.Model.Dto.Orders;
using Shop.Model.Entity.Order;
using Shop.Model.Entity.Use;
using System.Text.Json;

namespace Shop.Services
{
    /// <summary>
    /// 支付
    /// </summary>
    public class PaymentAppService : AbstractService, IPaymentAppService
    {
        private readonly RepositoryBase<Orders> _orderRepository;

        private readonly RepositoryBase<UseUser> _userRepository;

        private readonly RepositoryBase<UserWallet> _walletRepository;

        private readonly WeChatPayOptions _weChatPayConfig;

        private readonly CallBackUrlConfig _callBackUrlConfig;

        private readonly IWeChatPayClient _client;

        private readonly IUserContext _userContext;

        private readonly ICapPublisher _capPublisher;

        private readonly IRedisProvider _redisProvider;

        public PaymentAppService(RepositoryBase<Orders> orderRepository
            , RepositoryBase<UseUser> userRepository
            , RepositoryBase<UserWallet> walletRepository
            , IOptions<WeChatPayOptions> weChatPayConfig
            , IOptions<CallBackUrlConfig> callBackUrlConfig
            , IWeChatPayClient client
            , IUserContext userContext
            , ICapPublisher capPublisher
            , IRedisProvider redisProvider)
        {
            _orderRepository = orderRepository;
            _walletRepository = walletRepository;
            _userRepository = userRepository;
            _weChatPayConfig = weChatPayConfig.Value;
            _callBackUrlConfig = callBackUrlConfig.Value;
            _client = client;
            _userContext = userContext;
            _capPublisher = capPublisher;
            _redisProvider = redisProvider;
        }

        /// <summary>
        /// 支付
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public async Task<AppResult<object>> Pay(PayDto input)
        {
            var user = await _orderRepository.Queryable<UseUser>().FirstAsync(x => x.Id == _userContext.Id);
            var order = await _orderRepository.GetFirstAsync(x => x.Id == input.OrderId && x.UserId == _userContext.Id);

            if (input.PayMode == Payment.Balance && string.IsNullOrEmpty(user.WithdrawalPassword))
                throw new Exception("请先设置你的支付密码");

            if (input.PayMode == Payment.Balance && !user.WithdrawalPassword.Equals(CommonHelper.Md5For32(input.PayPwd)))
                throw new Exception("支付密码错误");

            if (order == null)
                throw new Exception("找不到订单!");
            if (order.OrderStatus != (int)OrderStatus.Normal)
                throw new Exception("订单已支付或已过期!");

            if (input.PayMode == Payment.Balance)
            {
                var result = await BalancePay(order.Id, order.OrderAmount);
                if (result.Data)
                {
                    order.OrderStatus = (int)OrderStatus.Paid;
                    order.PayChannel = (int)Payment.Balance;
                    order.PayTime = DateTime.Now;
                    await _orderRepository.UpdateAsync(order);
                    await _capPublisher.PublishAsync("Order.Payment", order.Id);
                    await _capPublisher.PublishAsync("Order.Push", order.Id);
                    return new AppResult<object>(200, "成功");
                }
                else
                    return new AppResult<object>(400, "失败",null,result.Errors[0]);
            }
            else if (input.PayMode == Payment.WaChatPay)
            {
                var data = YopPay.Pay(order.Code, user.AppletOpenId, order.OrderAmount, _callBackUrlConfig.YOPPay);
                if (data != null && data.code == "00000")
                    return JsonSerializer.Deserialize<PaymentParam>(data.prePayTn);                  

            }
            return new AppResult<object>(400,"支付失败");    
        }

        /// <summary>
        /// 微信支付回调
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public async Task WechatPayNotify(WeChatPayUnifiedOrderNotify input)
        {
            var order = await _orderRepository.GetFirstAsync(x => x.Code == input.OutTradeNo);
            if (order != null && order.OrderStatus == (int)OrderStatus.Normal)
            {
                order.OrderStatus = (int)OrderStatus.Paid;
                order.PayChannel = (int)Payment.WaChatPay;
                order.PayTime = DateTime.ParseExact(input.TimeEnd,"yyyyMMddHHmmss", System.Globalization.CultureInfo.CurrentCulture);
                order.TransactionId = input.TransactionId;
                await _orderRepository.UpdateAsync(order);
                await _capPublisher.PublishAsync("Order.Payment", order.Id);
                await _capPublisher.PublishAsync("Order.Push", order.Id);
            }
        }


        /// <summary>
        /// 易宝支付回调
        /// </summary>
        /// <returns></returns>
        public async Task<AppResult> YopPayNotify(NotifyDto input)
        {
            try
            {
                var data = YopPay.NoticeDecrypt(input.response);
                var notify = JsonSerializer.Deserialize<PaymentNotifyDto>(data);
                if (notify.status == "SUCCESS")
                {
                    var order = await _orderRepository.GetFirstAsync(x => x.Code == notify.orderId);
                    if (order != null && order.OrderStatus == (int)OrderStatus.Normal)
                    {
                        order.OrderStatus = (int)OrderStatus.Paid;
                        order.PayChannel = (int)Payment.WaChatPay;
                        order.PayTime = DateTime.Parse(notify.paySuccessDate);
                        order.TransactionId = notify.uniqueOrderNo;
                        await _orderRepository.UpdateAsync(order);
                        await _capPublisher.PublishAsync("Order.Payment", order.Id);
                        await _capPublisher.PublishAsync("Order.Push", order.Id);
                    }
                }
                return Success();
            }
            catch (Exception ex)
            {
                return Problem(ex.Message);
            }
        }


        /// <summary>
        /// 易宝转账回调
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public async Task<AppResult> YopTransferCallBack(NotifyDto input)
        {
            try
            {
                var data = YopPay.NoticeDecrypt(input.response);
                var notify = JsonSerializer.Deserialize<TransferNotifyDto>(data);
                var draw = await _userRepository.Queryable<UseDrawMoneyEntity>().FirstAsync(x => x.Id == int.Parse(notify.requestNo));
                if(draw == null || draw.State != 1)
                    return Success();
                if (notify.status == "SUCCESS")
                {
                    draw.State = 2;
                    draw.DisposeTime = DateTime.Now;
                    var walletLog = await _userRepository.Queryable<UserWalletLog>().FirstAsync(x => x.Id == draw.WalletLog);
                    walletLog.State = 1;
                    walletLog.FinishTime = DateTime.Now;
                    var wallet = await _userRepository.Queryable<UserWallet>().FirstAsync(x => x.UserId == draw.User);
                    _userRepository.Change<UserWallet>().AsUpdateable().Where(x => x.UserId == draw.User)
                        .SetColumns(x => x.Withdrawable == x.Withdrawable + walletLog.Amount)
                        .ExecuteCommand();
                    await _userRepository.Change<UserWalletLog>().UpdateAsync(walletLog);
                    await _userRepository.Change<UseDrawMoneyEntity>().UpdateAsync(draw);
                }
                else if(notify.status == "FAIL")
                {
                    draw.State = -1;
                    draw.DisposeTime = DateTime.Now;
                    draw.Title = notify.failReason;
                    var walletLog = await _userRepository.Queryable<UserWalletLog>().FirstAsync(x => x.Id == draw.WalletLog);
                    walletLog.State = -1;
                    walletLog.FinishTime = DateTime.Now;
                    _userRepository.Change<UserWallet>().AsUpdateable().Where(x => x.UserId == draw.User)
                    .SetColumns(x => x.Balance == x.Balance + walletLog.Amount)
                    .ExecuteCommand();
                    await _userRepository.Change<UserWalletLog>().UpdateAsync(walletLog);
                    await _userRepository.Change<UseDrawMoneyEntity>().UpdateAsync(draw);
                }
                return Success();
            }
            catch (Exception ex)
            {
                return Problem(ex.Message);
            }
        }

        public async Task<AppResult> YopTransferCallBack1(string data)
        {
            try
            {
                //var data = YopPay.NoticeDecrypt(input.response);
                //Console.WriteLine($"打款回调值：{data}");
                var notify = JsonSerializer.Deserialize<TransferNotifyDto>(data);
                var draw = await _userRepository.Queryable<UseDrawMoneyEntity>().FirstAsync(x => x.Id == int.Parse(notify.requestNo));
                if (draw == null || draw.State != 1)
                    return Success();
                if (notify.status == "SUCCESS")
                {
                    draw.State = 2;
                    draw.DisposeTime = DateTime.Now;
                    var walletLog = await _userRepository.Queryable<UserWalletLog>().FirstAsync(x => x.Id == draw.WalletLog);
                    walletLog.State = 1;
                    walletLog.FinishTime = DateTime.Now;
                    var wallet = await _userRepository.Queryable<UserWallet>().FirstAsync(x => x.UserId == draw.User);
                    _userRepository.Change<UserWallet>().AsUpdateable().Where(x => x.UserId == draw.User)
                        .SetColumns(x => x.Withdrawable == x.Withdrawable + walletLog.Amount)
                        .ExecuteCommand();
                    await _userRepository.Change<UserWalletLog>().UpdateAsync(walletLog);
                    await _userRepository.Change<UseDrawMoneyEntity>().UpdateAsync(draw);
                }
                else if (notify.status == "FAIL")
                {
                    draw.State = -1;
                    draw.DisposeTime = DateTime.Now;
                    draw.Title = notify.failReason;
                    var walletLog = await _userRepository.Queryable<UserWalletLog>().FirstAsync(x => x.Id == draw.WalletLog);
                    walletLog.State = -1;
                    walletLog.FinishTime = DateTime.Now;
                    _userRepository.Change<UserWallet>().AsUpdateable().Where(x => x.UserId == draw.User)
                    .SetColumns(x => x.Balance == x.Balance + walletLog.Amount)
                    .ExecuteCommand();
                    await _userRepository.Change<UserWalletLog>().UpdateAsync(walletLog);
                    await _userRepository.Change<UseDrawMoneyEntity>().UpdateAsync(draw);
                }
                return Success();
            }
            catch (Exception ex)
            {
                return Problem(ex.Message);
            }
        }

        /// <summary>
        /// 发起微信支付
        /// </summary>
        /// <param name="orderCode"></param>
        /// <param name="amount"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        private async Task<AppResult<object>> WaChatPay(string orderCode, decimal amount)
        {
            var user = await _userRepository.GetByIdAsync(_userContext.Id);
            var request = new WeChatPayUnifiedOrderRequest
            {
                Body = "商品支付",
                OutTradeNo = orderCode,
                TotalFee = Convert.ToInt32(amount * 100),
                SpBillCreateIp = "127.0.0.1",
                NotifyUrl = _callBackUrlConfig.WeChatPay,
                TradeType = "JSAPI",
                OpenId = user.AppletOpenId,
            };

            var response = await _client.ExecuteAsync(request, _weChatPayConfig);
            if (response.ReturnCode == WeChatPayCode.Success && response.ResultCode == WeChatPayCode.Success)
            {
                var req = new WeChatPayMiniProgramSdkRequest
                {
                    Package = "prepay_id=" + response.PrepayId
                };

                var parameter = await _client.ExecuteAsync(req, _weChatPayConfig);

                // 将参数(parameter)给 小程序前端 让他调起支付API(https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=5)
                return parameter;
            }
            else
                return new AppResult<object>(400, "建立微信支付请求失败");
        }

        /// <summary>
        /// 余额支付
        /// </summary>
        /// <param name="orderId">订单Id</param>
        /// <param name="amount">金额</param>
        /// <returns></returns>
        private async Task<AppResult<bool>> BalancePay(long orderId, decimal amount)
        {
            var cacheKey = "wallet:" + _userContext.Id;
            var flag = await _redisProvider.LockAsync(cacheKey);
            if (!flag.Success)
            {
                await Task.Delay(300);
                flag = await _redisProvider.LockAsync(cacheKey);
            }
            try
            {
                var wallet = await _walletRepository.GetFirstAsync(x => x.UserId == _userContext.Id);
                if (wallet == null || wallet.Balance < amount)
                    return new AppResult<bool>(400,"失败",false, "钱包余额不足");
                wallet.Balance -= amount;
                await _walletRepository.UpdateAsync(wallet);
                await _walletRepository.Change<UserWalletLog>().InsertAsync(new UserWalletLog
                {
                    UserId = _userContext.Id,
                    Action = (int)GlobalEnum.UserWalletSourceTypes.Pay,
                    State = 1,
                    Title = $"消费了{amount}元",
                    Amount = amount,
                    SourceOrder = orderId,
                    FinishTime = DateTime.Now,
                });
                return new AppResult<bool>(200, "成功", true, "支付成功");
            }
            catch (Exception)
            {
                return new AppResult<bool>(400, "失败", true, "支付成功");
            }
            finally
            {
                await _redisProvider.SafedUnLockAsync(cacheKey, flag.LockValue);
            }          
        }

        /// <summary>
        /// 结束
        /// </summary>
        /// <param name="orderId"></param>
        /// <returns></returns>
        public async Task Settlement(long orderId)
        {
            var data = await _walletRepository.Queryable<UserWalletLog>()
                .Where(x => x.SourceOrder == orderId && (x.Action == (int)WalletSourceTypes.Distribution || x.Action == (int)WalletSourceTypes.Prize) && x.State == 0)
                .ToListAsync();
            if (data.Count > 0)
            {
                foreach (var item in data)
                {
                    var cacheKey = "wallet:" + item.UserId;
                    var flag = await _redisProvider.LockAsync(cacheKey);
                    if (!flag.Success)
                    {
                        await Task.Delay(300);
                        flag = await _redisProvider.LockAsync(cacheKey);
                    }
                    try
                    {
                        var wallet = await _walletRepository.GetFirstAsync(x => x.UserId == item.UserId);
                        if (wallet == null || wallet.Surplus < item.Amount)
                            break;
                        wallet.Balance += item.Amount;
                        wallet.Surplus -= item.Amount;
                        await _walletRepository.UpdateAsync(wallet);
                        item.State = 1;
                        item.FinishTime = DateTime.Now;
                    }
                    finally
                    {
                        await _redisProvider.SafedUnLockAsync(cacheKey, flag.LockValue);
                    }
                }

                await _walletRepository.Change<UserWalletLog>().UpdateRangeAsync(data);
            }
        }

        /// <summary>
        /// 完成订单支付
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        public AppResult AdminPay(AdminPayDto dto)
        {
            var order = _orderRepository.GetFirst(x => x.Id == dto.Id);
            if (order == null)
                return Problem ("找不到订单!");
            if (order.OrderStatus != (int)OrderStatus.Normal)
                return Problem("订单已支付或已过期!");
            order.OrderStatus = (int)OrderStatus.Paid;
            order.PayChannel = (int)Payment.OfflinePay;
            order.PayTime = DateTime.Now;
            _orderRepository.Update(order);
             _capPublisher.Publish("Order.Payment", order.Id);
            return Success();
        }
    }
}
